package com.yj.spring.charpter01;

import cn.hutool.core.collection.CollUtil;
import com.yj.spring.common.BeanDefinition;
import com.yj.spring.common.PropertyValue;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ClassPathXmlApplicationContext {

    private String[] configLocations;


    private List<BeanDefinition> beanDefinitions = new ArrayList<>();;

    private Map<String, Object> singletonObjects = new HashMap<>();

    public ClassPathXmlApplicationContext(String[] configLocations) {
        this.configLocations = configLocations;
    }

    public ClassPathXmlApplicationContext(String configLocations) {
        this.configLocations = new String[]{configLocations};
    }

    /**
     * 刷新容器
     */
    public void refresh(){
        // 解析配置文件
        parseConfigLocations();
    }

    private void parseConfigLocations() {
        if (configLocations != null){
            for (String configLocation : configLocations) {
                doParseConfigLocation(configLocation);
            }
        }
    }

    private void doParseConfigLocation(String configLocation) {
        SAXReader saxReader = new SAXReader();
        URL resource = this.getClass().getClassLoader().getResource(configLocation);
        if(resource == null){
            throw new IllegalArgumentException("Config location " + configLocation + " not found");
        }
        try {
            Document document = saxReader.read(resource);
            Element beans = document.getRootElement();
            List beanList = beans.elements();
            for (Object bean : beanList) {
                Element element = (Element) bean;
                String id = element.attribute("id").getValue();
                String className = element.attribute("class").getValue();

                List<Element> propertys = element.elements("property");
                List<PropertyValue> propertyValues = new ArrayList<>();
                if (CollUtil.isNotEmpty(propertys)){

                    for (Element property : propertys) {
                        String name = property.attribute("name").getValue();
                        String value = property.attribute("value").getValue();
                        String type = property.attribute("type").getValue();
                        PropertyValue propertyValue = new PropertyValue(name, value, type);
                        propertyValues.add(propertyValue);
                    }
                }

                BeanDefinition beanDefinition = new BeanDefinition(id, className);
                if (CollUtil.isNotEmpty(propertyValues)){
                    beanDefinition.setPropertyValues(propertyValues);
                }
                beanDefinitions.add(beanDefinition);
            }
            doCreateSingletons();
        }catch (Exception e){
            throw new IllegalArgumentException("Parse config location " + configLocation + " error", e);
        }
    }

    private void doCreateSingletons() {
        if (CollUtil.isNotEmpty(beanDefinitions)){
            for (BeanDefinition beanDefinition : beanDefinitions) {
                // 注册bean
                Object bean = registerBean(beanDefinition);
                // 缓存bean
                singletonObjects.put(beanDefinition.getId(), bean);
            }
        }
    }

    private Object registerBean(BeanDefinition beanDefinition) {

        Object bean = null;
        try{
            String className = beanDefinition.getClassName();
            Class<?> clazz = Class.forName(className);
            bean = clazz.newInstance();
            if (CollUtil.isNotEmpty(beanDefinition.getPropertyValues())){
                for (PropertyValue propertyValue : beanDefinition.getPropertyValues()) {
                    Field field = clazz.getDeclaredField(propertyValue.getName());
                    field.setAccessible(true);
                    field.set(bean, propertyValue.getValue());
                }
            }
            return bean;
        }catch(Exception e){
            throw new IllegalArgumentException("Create bean " + beanDefinition.getId() + " error", e);
        }
    }

    public Object getBean(String beanName){
        Object o = singletonObjects.get(beanName);
        if(o == null){
            throw new IllegalArgumentException("Bean not found with name " + beanName);
        }
        return o;
    }
}
